﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Threading;
using System.Text.RegularExpressions;
using RoMBot.Classes;
using RoMBot.MemoryLib;
using RoMBot.Waypoints;

namespace RoMBot
{
    public enum WaypointType
    {
        Default,
        Travel,
        Run,
        Harvest,
        Wander
    }

    public enum WaypointMode
    {
        Waypoints,
        Wander
    }

    public enum WaypointListDirection
    {
        Forward,
        Backward
    }

    public enum SkillType
    {
        None,
        Damage,
        Buff,
        Debuff,
        DoT,
        HoT,
        Heal,
        Summon
    }

    public enum SkillTargetType
    {
        None = 0,
        Self = 1,
        Pet = 2,
        Friendly = 4,
        Enemy = 6
    }

    public enum RequiredTargetType
    {
        None,
        Player,
        Target
    }

    public enum AoECenter
    {
        Player,
        Target
    }

    public enum PowerType
    {
        None = 0,
        Mana = 2,
        HpPercent = 3,
        MpPercent = 4,
        Rage = 5,
        Focus = 6,
        Energy = 7,
        Item = 9,
        Proyectile = 13,
        Arrow = 14,
        Psi = 15
    }

    public enum ObjectType
    {
        None,
        Player,
        Monster,
        Sigil,
        NpcOrNode,
    }

    public class Helpers
    {
        internal const int EmptyNameMarker = 0x008FD0A6; // 0x008FD0A6 es un marcador que indica que no hay nombre
        private static readonly Dictionary<string, int> RomanNumbers = new Dictionary<string, int>
                                                                       {
                                                                               {"I", 1},
                                                                               {"II", 2},
                                                                               {"III", 3},
                                                                               {"IV", 4},
                                                                               {"V", 5},
                                                                               {"VI", 6},
                                                                               {"VII", 7},
                                                                               {"VIII", 8},
                                                                               {"IX", 9},
                                                                               {"X", 10},
                                                                       };


        public static float Distance( float x1, float y1, float x2, float y2 )
        {
            return ( float ) Math.Sqrt( ( y2 - y1 ) * ( y2 - y1 ) + ( x2 - x1 ) * ( x2 - x1 ) );
        }

        public static float Distance( float x1, float z1, float? y1, float x2, float z2, float? y2 )
        {
            if ( y1 == null || y2 == null )
            {
                return ( float ) Math.Sqrt( ( z2 - z1 ) * ( z2 - z1 ) + ( x2 - x1 ) * ( x2 - x1 ) );
            }
            return ( float ) Math.Sqrt( ( double ) ( ( z2 - z1 ) * ( z2 - z1 ) + ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) ) );
        }

        public static float Distance( Waypoint wp1, Waypoint wp2 )
        {
            if ( null == wp1 || null == wp2 )
                return 0;
            return Distance( wp1.X, wp1.Z, wp1.Y, wp2.X, wp2.Z, wp2.Y );
        }

        public static float Distance( float x, float z, Waypoint wp )
        {
            if ( null == wp )
                return 0;
            return Distance( x, z, wp.X, wp.Z );
        }

        public static float Distance( GameObject obj1, GameObject obj2 )
        {
            if ( null == obj1 || null == obj2 )
                return 0;
            return Distance( obj1.X, obj1.Z, obj2.X, obj2.Z );
        }

        public static float Distance( GameObject obj1, float x, float z )
        {
            return null == obj1 ? 0 : Distance( obj1.X, obj1.Z, x, z );
        }

        public static float Distance( GameObject pawn, Waypoint wp2 )
        {
            if ( null == pawn || null == wp2 )
                return 0;
            return Distance( pawn.X, pawn.Z, pawn.Y, wp2.X, wp2.Z, wp2.Y );
        }

        public static double AngleDifference( double angle1, double angle2 )
        {
            if ( Math.Abs( angle2 - angle1 ) > Math.PI )
                return ( Math.PI * 2 ) - Math.Abs( angle2 - angle1 );

            return Math.Abs( angle2 - angle1 );
        }

        public static double Rad( double degreeAngle )
        {
            return Math.PI * degreeAngle / 180.0;
        }

        public static double Deg( double angle )
        {
            return angle * ( 180.0 / Math.PI );
        }

        public static double GameTime
        {
            get { return Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, Addresses.gameTimeAddress ) / 1000d; }
        }

        public static KeyValuePair<int, int> ZoneId
        {
            get
            {
                var zonechannel = Memory.ReadRepeat<int>( Bot.Instancia.ProcId, Addresses.zoneId );
                if ( zonechannel != 0 )
                {
                    var zone = zonechannel % 1000;
                    return new KeyValuePair<int, int>( zone, ( zonechannel - zone ) / 1000 + 1 ); // zone and channel
                }
                Bot.Error( "Failed to get zone id." );
                return new KeyValuePair<int, int>( -1, -1 );
            }
        }

        public static void Yrest( int amount )
        {
            Thread.CurrentThread.Join( amount );
            Thread.Yield();//We yield once more before returning...
        }

        public static bool WaitForLoadingScreen( int maxSeconds = 5 )
        {
            //var oldAddress = Bot.Player.Address;
            //var newAddress = 0;
            Bot.Print( "Waiting for loading screen...", Color.Violet );
            var start = Stopwatch.StartNew();
            do
            {
                if ( start.Elapsed.TotalSeconds > maxSeconds )
                {
                    Bot.Error( "Loading screen didn't appear..." );
                    return false;
                }

                Yrest( 500 );
                //newAddress = ( int ) Memory.ReadRepeatPtr<uint>( Addresses.staticbase_char, Addresses.charPtr_offset );
            } while ( !IsInLoadingScreen );

            Bot.Print( "Loading screen apeared, waiting to finnish loading...", Color.Violet );
            do
            {
                Yrest( 500 );
            } while ( IsInLoadingScreen );

            Yrest( 2000 );
            Bot.Player.Update();
            return true;
        }

        public static bool IsInLoadingScreen
        {
            get { return Memory.ReadBytePtr( Addresses.loadingScreenPtr, Addresses.loadingScreen_offset ) != 0; }
        }

        public static object RoMCode( string code )
        {
            return code.Length < 254 - 41 ? RoMScript( "} " + code + " if type(a)~=\"table\" then a={a} end z={" ) : RoMScript( "} " + code + " z={" );
        }

        public static T RoMScript<T>( string script )
        {
            var ret = RoMScript( script );
            try
            {
                return ret != null ? ( T ) ret : default( T );
            }
            catch
            {
                return default( T );
            }
        }

        public static object RoMScript( string script )
        {
            if ( !IsInGame )
            {
                return null;
            }

            if ( Bot.Instancia.CommandMacro == 0 )
            {
                return null;
            }

            var cMacro = Macros.ReadMacro( Bot.Instancia.CommandMacro );
            var rMacro = Macros.ReadMacro( Bot.Instancia.ResultMacro );

            if ( cMacro.Name != Macros.CommandMacroName || rMacro.Name != Macros.ResultMacroName )
            { // macros moved
                Macros.SetupMacros();
            }
            // Get the real address
            var macroAddress = Memory.ReadUInt( Addresses.staticbase_macro );

            var dataPart = 0; // The part of the data to get
            var raw = "";     // Combined raw data from 'R'
            string rawPart;     // Combined raw data from 'R'

            do
            {
                // The command macro
                var text = dataPart == 0 ? script : "SendMore";

                // Check to make sure length is within bounds
                var len = text.Length;
                if ( len > 254 )
                {
                    throw new Exception( "Macro text too long by " + ( len - 254 ) );
                }

                bool tryagain;
                do
                {
                    tryagain = false;
                    // Write the command macro
                    Macros.WriteToMacro( Bot.Instancia.CommandMacro, text );

                    // Write something on the first address, to see when its over written
                    Memory.WriteByte( ( int ) macroAddress + Addresses.macroSize * ( Bot.Instancia.ResultMacro - 1 ) + Addresses.macroBody_offset, 6 );

                    // Execute it
                    if ( Profile.HotKeys.Macro.Key != Key.VK_NONE )
                    {
                        Keyboard.KeyPress( Profile.HotKeys.Macro.Key );
                    }

                    var startWaitTime = DateTime.Now;
                    while ( Memory.ReadByte( ( int ) macroAddress + Addresses.macroSize * ( Bot.Instancia.ResultMacro - 1 ) + Addresses.macroBody_offset ) == 6 )
                    {
                        var now = DateTime.Now;
                        if ( ( now - startWaitTime ).TotalMilliseconds > 500 )
                        {
                            Bot.Instancia.Log += "[DEBUG] Timeout in RoMScript";
                            if ( Memory.ReadUInt( Addresses.editBoxHasFocus_address ) == 0 )
                            {
                                Keyboard.KeyPress( Key.VK_ESCAPE );
                                Thread.Sleep( 500 );
                                var r = RoMScript( "GameMenuFrame:IsVisible()" );
                                if ( r != null && Convert.ToBoolean( r ) )
                                {
                                    // Clear the game menu and reset editbox focus
                                    Keyboard.KeyPress( Key.VK_ESCAPE );
                                    Thread.Sleep( 500 );

                                    //keyboardPress(settings.hotkeys.ESCAPE.key); yrest(300)
                                    RoMScript( "z = GetKeyboardFocus(); if z then z:ClearFocus() end" );
                                }
                            }

                            tryagain = true;
                            //Bot.Debug( string.ItemLinkFormat( "RoMScript will try again because of timeout ( {0} )", ( now - startWaitTime ).TotalMilliseconds ) );
                            break; // Timed out
                        }
                        Thread.CurrentThread.Join( 10 );
                    }
                } while ( tryagain );

                // Read the outcome from the result macro
                rawPart = Macros.ReadMacro( Bot.Instancia.ResultMacro ).Body;

                raw += rawPart;

                dataPart = dataPart + 1;
                //Bot.Debug( string.ItemLinkFormat( "RoMScript: rawPart.Lenght = {0}", rawPart.Length ) );
            } while ( rawPart.Length > 255 );

            var ret = new List<object>();
            var result = raw.Split( new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries );
            foreach ( var r in result )
            {
                if ( r.Contains( "." ) )
                {
                    float fTemp;
                    if ( float.TryParse( r, NumberStyles.AllowDecimalPoint, NumberFormatInfo.InvariantInfo, out fTemp ) )
                    {
                        ret.Add( fTemp );
                        continue;
                    }
                }
                //Int first, because if it is a number and its 0 or 1 it will be converted to bool.
                int iTemp;
                if ( int.TryParse( r, out iTemp ) )
                {
                    ret.Add( iTemp );
                    continue;
                }
                bool bTemp;
                if ( bool.TryParse( r, out bTemp ) )
                {
                    ret.Add( bTemp );
                    continue;
                }
                ret.Add( r );
            }
            if ( ret.Count > 0 )
            {
                try
                {
                    if ( !Convert.ToBoolean( ret[ 0 ] ) )
                    {
                        //throw new Exception( "IGF:\\" + script + "\\ " + ret[ 1 ] );
                        Bot.Error( "IGF:\\" + script + "\\ " + ret[ 1 ] );
                    }

                }
                catch ( FormatException fe )
                {
                    Bot.Error( "IGF: returned an invalid value: " + fe.Message );
                }
                catch ( InvalidCastException ic )
                {
                    Bot.Error( "IGF: returned an invalid value: " + ic.Message );
                }
                ret.RemoveAt( 0 );
            }
            return ret.Count == 1 ? ret[ 0 ] : ret.Count == 0 ? null : ret;
        }

        public void SlashCommand( string commandText )
        {
            if ( Bot.Instancia.CommandMacro == 0 )
            {
                return;
            }
            if ( string.IsNullOrWhiteSpace( commandText ) )
            {
                Bot.Error( "commandText is empty" );
                return;
            }

            if ( !commandText.StartsWith( "/" ) )
            {
                commandText = "/" + commandText;
            }

            Macros.WriteToMacro( Bot.Instance.CommandMacro, commandText );
            Thread.Sleep( 0 );
            // Execute it
            if ( Profile.HotKeys.Macro.Key != Key.VK_NONE )
            {
                Keyboard.KeyPress( Profile.HotKeys.Macro.Key );
            }
        }

        /// <summary>
        /// Sends a message to the game chat.
        /// </summary>
        /// <param name="message">The message to be send.</param>
        public static void SendMessage( string message )
        {
            message = message.Replace( "\n", "\\n" ).Replace( "\"", "\\\"" );

            RoMScript( "ChatFrame1:AddMessage(\"" + message + "\")" );
        }

        public static ItemLink ParseItemLink( string link )
        {
            if ( string.IsNullOrWhiteSpace( link ) )
            {
                return null;
            }
            var vals = link.Split( new[] { '|' }, StringSplitOptions.RemoveEmptyEntries );
            var id = int.Parse( vals[ 0 ].Split( ':' )[ 1 ], NumberStyles.HexNumber );
            var color = long.Parse( vals[ 2 ].Split( '[' )[ 0 ], NumberStyles.HexNumber );
            var name = vals[ 2 ].Split( '[' )[ 1 ].Replace( "]", "" );

            return new ItemLink { Id = id, Color = color, Name = name };
        }

        public static string GetNumberFromStr( string str )
        {
            if ( string.IsNullOrEmpty( str ) )
            {
                return null;
            }
            str = str.Trim();
            var m = Regex.Match( str, @"^[\+\-]?\d*\.?[Ee]?[\+\-]?\d*$" );
            return m.Value;
        }

        public static bool BitAnd( long test, long pattern )
        {
            return ( test & pattern ) != 0;
        }

        public static int BitAndInt( long test, long pattern )
        {
            return ( int ) ( test & pattern );
        }

        public static T GetConvertedValue<T>( object val )
        {
            var type = typeof( T );
            if ( type == typeof( string ) && val is string )
            {
                return ( T ) val;
            }
            if ( type == typeof( string ) && !( val is string ) )
            {
                return ( T ) ( object ) Convert.ToString( val );
            }
            if ( type.IsPrimitive )
            {
                if ( type == typeof( bool ) )
                {
                    return ( T ) ( object ) Convert.ToBoolean( val );
                }
                if ( type == typeof( bool? ) )
                {
                    return ( T ) ( object ) ( val == null ? ( bool? ) null : Convert.ToBoolean( val ) );
                }
                if ( type == typeof( byte ) )
                {
                    return ( T ) ( object ) Convert.ToByte( val );
                }
                if ( type == typeof( sbyte ) )
                {
                    return ( T ) ( object ) Convert.ToSByte( val );
                }
                if ( type == typeof( Int16 ) )
                {
                    return ( T ) ( object ) Convert.ToInt16( val );
                }
                if ( type == typeof( UInt16 ) )
                {
                    return ( T ) ( object ) Convert.ToUInt16( val );
                }
                if ( type == typeof( Int32 ) )
                {
                    return ( T ) ( object ) Convert.ToInt32( val );
                }
                if ( type == typeof( Int32? ) )
                {
                    return ( T ) ( object ) ( val != null ? Convert.ToInt32( val ) : ( Int32? ) null );
                }
                if ( type == typeof( UInt32 ) )
                {
                    return ( T ) ( object ) Convert.ToUInt32( val );
                }
                if ( type == typeof( Int64 ) )
                {
                    return ( T ) ( object ) Convert.ToInt64( val );
                }
                if ( type == typeof( UInt64 ) )
                {
                    return ( T ) ( object ) Convert.ToUInt64( val );
                }
                if ( type == typeof( char ) )
                {
                    return ( T ) ( object ) Convert.ToChar( val );
                }
                if ( type == typeof( double ) )
                {
                    return ( T ) ( object ) Convert.ToDouble( val );
                }
                if ( type == typeof( Single ) )
                {
                    return ( T ) ( object ) Convert.ToSingle( val );
                }
            }

            return default( T );
        }

        public static bool IsInGame
        {
            get
            {
                return Memory.ReadBytePtr( Addresses.loadingScreenPtr, Addresses.loadingScreen_offset ) == 0 &&
                Memory.ReadInt( Addresses.isInGame ) == 1;
            }
        }

        public static Waypoint GetNearestSegmentPoint( double x, double z, double a, double b, double c, double d )
        {
            if ( Math.Abs( a - c ) < double.Epsilon && Math.Abs( b - d ) < double.Epsilon )
            {
                return new Waypoint( ( float ) a, ( float ) b );
            }

            var dx1 = x - a;
            var dz1 = z - b;
            var dx2 = c - a;
            var dz2 = d - b;

            var dot = dx1 * dx2 + dz1 * dz2;
            var lenSq = dx2 * dx2 + dz2 * dz2;
            var param = dot / lenSq;

            double nx, nz;

            if ( param < 0 )
            {
                nx = a;
                nz = b;
            }
            else if ( param > 1 )
            {
                nx = c;
                nz = d;
            }
            else
            {
                nx = a + param * dx2;
                nz = b + param * dz2;
            }

            return new Waypoint( ( float ) nx, ( float ) nz );
        }

        public static string GetText( string text )
        {
            if ( string.IsNullOrEmpty( text ) )
            {
                return null;
            }

            var resultText = RoMScript( "TEXT(\"" + text + "\")" ) as string;
            if ( !string.IsNullOrEmpty( resultText ) )
            {
                var r = Regex.Matches( resultText, "\\[(.*)\\]" );

                foreach ( Match subText in r )
                {
                    var translatedSubText = RoMScript( "TEXT(\"" + subText + "\")" ) as string;
                    if ( translatedSubText != subText.Result( "" ) )
                    {
                        resultText = resultText.Replace( "[" + subText + "]", translatedSubText );
                    }
                }

            }
            return resultText;
        }

        public static float GetLastWarning( string message, int age = 0 )
        {
            if ( string.IsNullOrEmpty( message ) )
            {
                return 0f;
            }
            List<object> resu;
            if ( age > 0 )
            {
                resu = RoMScript<List<object>>( "igf_events:getLastEventMessage('WARNING_MESSAGE','" + message + "'," + age + ")" );
                return resu != null ? Convert.ToSingle( resu[ 0 ] ) : 0;
            }

            resu = RoMScript<List<object>>( "igf_events:getLastEventMessage('WARNING_MESSAGE','" + message + "')" );
            return resu != null ? Convert.ToSingle( resu[ 0 ] ) : 0;
        }

        public static int GetPing()
        {
            return Memory.ReadIntPtr( Addresses.staticbase_char, Addresses.ping_offset );
        }

        /// <summary>
        /// Select Option By Name
        /// </summary>
        /// <remarks>
        /// NPC option dialog should already be open
        /// </remarks>
        /// <param name="optiontext">The option text or part of.</param>
        /// <returns>true if we can choose, false otherwise.</returns>
        public static bool ChoiceOptionByName( string optiontext )
        {
            if ( !Convert.ToBoolean( RoMScript( "SpeakFrame:IsVisible()" ) ) )
            {
                Bot.Print( "Please open a dialog before using \"ChoiceOptionByName\"." );
                return false;
            }
            var counter = 1;
            string option;

            do
            {
                var resu = RoMScript( "GetSpeakOption(" + counter + ")" ) as List<object>;
                option = resu != null ? ( string ) resu[ 0 ] : null;
                if ( !string.IsNullOrEmpty( option ) && FindNormalisedString( option, optiontext ) )
                {
                    // First try "ChoiceOption"
                    RoMScript( "ChoiceOption(" + counter + ");" );
                    Thread.CurrentThread.Join( 1000 );

                    // If SpeakFrame is still open && option is still there { try "SpeakFrame_ListDialogOption"
                    resu = RoMScript( "GetSpeakOption(" + counter + ")" ) as List<object>;
                    option = resu != null ? ( string ) resu[ 0 ] : null;
                    if ( !string.IsNullOrEmpty( option ) && FindNormalisedString( option, optiontext ) && Convert.ToBoolean( RoMScript( "SpeakFrame:IsVisible()" ) ) )
                    {
                        RoMScript( "SpeakFrame_ListDialogOption(1," + counter + ");" );
                        Thread.CurrentThread.Join( 1000 );
                    }
                    return true;
                }
                counter++;
            } while ( !string.IsNullOrEmpty( option ) );

            Bot.Printf( Color.Tomato, "\"ChoiceOptionByName\", option not found: {0}", optiontext );

            return false;
        }

        public static string NormaliseString( string str )
        {
            return Regex.Replace( str, "\\^+\\$+\\(+\\)+\\%+\\[+\\]+\\*+\\++\\-+\\?+", "" ).ToLower();
        }

        public static bool FindNormalisedString( string str, string name )
        {
            name = name.ToLower();
            str = NormaliseString( str );
            return str.Contains( name );
        }

        public static void SwitchClass( PawnClass from, PawnClass to )
        {
            RoMScript( "ExchangeClass(" + ( ( int ) from + 1 ) + "," + ( ( int ) to + 1 ) + ")" );
        }

        public static bool AcceptQuestByName( object nameorid, object questgroup = null )
        {
            // Check for valid _nameorid
            string questToAccept;
            if ( nameorid == null )
            {
                questToAccept = "all";
            }
            else if ( nameorid is string )
            {
                questToAccept = nameorid.ToString();
            }
            else
            {
                questToAccept = MemoryTable.GetIdName( ( int ) nameorid );
            }

            // Check for valid _questgroup
            if ( questgroup != null )
            {
                if ( questgroup is string )
                {
                    questgroup = questgroup.ToString().ToLower().TrimEnd( 's' );
                }
                switch ( ( string ) questgroup )
                {
                    case "normal":
                        questgroup = 0;
                        break;
                    case "daily":
                        questgroup = 2;
                        break;
                    case "public":
                        questgroup = 3;
                        break;
                    default:
                        questgroup = null;
                        break;
                }
            }

            // If no _questgroup specified && Id used, get quest group from memory.
            if ( questgroup == null && nameorid is int )
            {
                var baseaddress = MemoryTable.GetItemAddress( ( int ) nameorid );
                if ( baseaddress != 0 )
                {
                    questgroup = Memory.ReadInt( baseaddress + Addresses.questGroup_offset );
                }
            }

            // Check if we have target
            Thread.CurrentThread.Join( 100 );
            if ( Bot.Player.targetPtr == 0 )
            {
                Bot.Print( "No target! Target NPC before using AcceptQuestByName" );
                return false;
            }

            // Target NPC again to get updated questlist
            Bot.Player.Attack();
            Thread.CurrentThread.Join( 500 );

            var availableQuests = ( int ) RoMScript( "GetNumQuest(1)" ); // Get number of available quests
            //if DEBUG {
            //    //printf("Number of available quests: %d\n",availableQuests)
            //}

            var matchFound = false;
            // For each quest
            for ( var i = 1; i <= availableQuests; i++ )
            {
                // Check to see if we have room to accept quests
                if ( 30 > RoMScript<int>( "GetNumQuestBookButton_QuestBook()" ) )
                {
                    var resu = RoMScript<List<object>>( "GetQuestNameByIndex(1, " + i + ")" );
                    if ( resu == null )
                    {
                        continue;
                    }
                    // Get quest name
                    var questOnBoard = resu[ 0 ].ToString();
                    //var daily = resu[ 1 ];
                    var qgroup = resu[ 2 ];
                    if ( ( questToAccept == "" || questToAccept == "all" ) || // Accept all
                         FindNormalisedString( questOnBoard, questToAccept )// Or match name
                    && ( questgroup == null || questgroup == qgroup ) )// And match quest group
                    {
                        matchFound = true;
                        do
                        {
                            RoMScript( "OnClick_QuestListButton(1," + i + ")" ); // Clicks the quest
                            Thread.CurrentThread.Join( 100 );
                            RoMScript( "AcceptQuest()" ); // Accepts the quest
                            Thread.CurrentThread.Join( 100 );
                        } while ( GetQuestStatus( questOnBoard ) != "incomplete" && GetQuestStatus( questOnBoard ) != "complete" ); // Try again if accepting didn't work
                        Bot.Printf( "Quest accepted: {0}", questOnBoard );

                        // break if name matched
                        if ( questToAccept != "" && questToAccept != "all" )
                        {
                            break;
                        }
                        Thread.CurrentThread.Join( 200 );
                    }
                    else if ( questToAccept != "" && questToAccept != "all" && i == availableQuests )
                    {
                        // Didn't find name match
                        Bot.Printf( "Quest name: {0} not found.", questToAccept );
                    }
                }
                else
                {
                    Bot.Print( "Maximum number of quests in questbook." );
                }
            }
            RoMScript( "SpeakFrame:Hide()" );
            Thread.CurrentThread.Join( 500 + Ping );
            return matchFound;
        }

        public static bool CompleteQuestByName( object nameorid, object rewardnumberorname = null, object questgroup = null )
        {

            // Check for valid _nameorid
            object questToComplete;
            if ( nameorid == null )
            {
                questToComplete = "all";
            }
            else if ( nameorid is int )
            {
                questToComplete = MemoryTable.GetIdName( ( int ) nameorid );
            }
            else
            {
                questToComplete = nameorid;
            }

            if ( !( questToComplete is string ) )
            {
                throw new Exception( "Invalid name || id used in CompleteQuestByName" );
            }

            // Check if user put questgroup in second argument
            if ( rewardnumberorname is string &&
               ( rewardnumberorname.ToString().ToLower() == "normal" || rewardnumberorname.ToString().ToLower() == "daily" || rewardnumberorname.ToString().ToLower() == "public" ) )
            {
                questgroup = rewardnumberorname;
                rewardnumberorname = null;
            }

            // Check for valid _questgroup
            if ( questgroup != null )
            {
                if ( questgroup is string )
                {
                    questgroup = questgroup.ToString().ToLower().TrimEnd( 's' );
                }
                switch ( ( string ) questgroup )
                {
                    case "normal":
                        questgroup = 0;
                        break;
                    case "daily":
                        questgroup = 2;
                        break;
                    case "public":
                        questgroup = 3;
                        break;
                    default:
                        questgroup = null;
                        break;
                }
            }

            // If no _questgroup specified && Id used, get quest group from memory.
            if ( questgroup == null && nameorid is int )
            {
                var baseaddress = MemoryTable.GetItemAddress( ( int ) nameorid );
                if ( baseaddress != 0 )
                {
                    questgroup = Memory.ReadInt( baseaddress + Addresses.questGroup_offset );
                }
            }

            // Check if we have target
            Thread.CurrentThread.Join( 100 );
            if ( Bot.Player.TargetPtr == 0 )
            {
                return false;
            }

            // Target NPC again to get updated questlist
            Bot.Player.Attack();
            Thread.CurrentThread.Join( 500 );

            var availableQuests = ( int ) RoMScript( "GetNumQuest(3)" );

            // For each quest
            for ( var i = 1; i <= availableQuests; i++ )
            {
                // Get quest name
                var resu = RoMScript<List<object>>( "GetQuestNameByIndex(3, " + i + ")" );
                if ( resu == null )
                {
                    continue;
                }
                // Get quest name
                var questOnBoard = resu[ 0 ].ToString();
                //var daily = resu[ 1 ];
                var qgroup = resu[ 2 ];

                if ( ( ( string ) questToComplete == "" || ( string ) questToComplete == "all" ) || // Complete all
                  FindNormalisedString( questOnBoard, ( string ) questToComplete ) && // Or match name
                  ( questgroup == null || questgroup == qgroup ) )
                { // And match quest group
                    var counttime = DateTime.Now;
                    do
                    {
                        RoMScript( "OnClick_QuestListButton(3, " + i + ")" ); // Clicks the quest
                        Thread.CurrentThread.Join( 100 );

                        if ( rewardnumberorname != null )
                        {
                            if ( rewardnumberorname is int )
                            {
                                RoMScript( "SpeakFrame_ClickQuestReward(SpeakQuestReward1_Item" + rewardnumberorname + ")" );
                                Thread.CurrentThread.Join( 100 );
                            }
                            else
                            {
                                var s = rewardnumberorname as string;
                                if ( s != null )
                                {
                                    // Search for reward name
                                    var found = false;
                                    for ( var rewardNum = 1; rewardNum <= ( int ) RoMScript( "SpeakQuestReward1.itemcount" ); rewardNum++ )
                                    {
                                        // rewardID = Helpers.RoMScript("SpeakQuestReward1_Item"+rewardNum+".ID")
                                        // rewardType = Helpers.RoMScript("SpeakQuestReward1_Item"+rewardNum+".Type")
                                        // set Tooltip
                                        // Helpers.RoMScript("GameTooltip:SetQuestItem("+rewardType+","+rewardID+")")
                                        // get Tooltip data
                                        var rewardName = ( string ) RoMScript( "SpeakQuestReward1_Item" + rewardNum + "_Desc:GetText()" );
                                        if ( FindNormalisedString( rewardName, s ) )
                                        {
                                            found = true;
                                            RoMScript( "SpeakFrame_ClickQuestReward(SpeakQuestReward1_Item" + rewardNum + ")" );
                                            Thread.CurrentThread.Join( 100 );
                                            break;
                                        }
                                    }
                                    if ( !found )
                                    {
                                        Bot.Printf( "Invalid reward name or number: {0}", s );
                                        return false;
                                    }
                                }
                                else
                                {
                                    Bot.Printf( "Invalid reward type specified, expected 'int' or 'string', got: {0}", rewardnumberorname.GetType().Name );
                                    return false;
                                }
                            }
                        }
                        else if ( ( DateTime.Now - counttime ).Seconds >= 2 )
                        { // quest still there because of reward item needs choosing
                            RoMScript( "SpeakFrame_ClickQuestReward(SpeakQuestReward1_Item1)" );
                            Thread.CurrentThread.Join( 100 );
                        }
                        RoMScript( "CompleteQuest()" ); // Completes the quest
                        Thread.CurrentThread.Join( 100 );
                    } while ( GetQuestStatus( questOnBoard ) != "complete" );

                    Bot.Printf( "Quest completed: {0}", questOnBoard );

                    // break if name matched
                    if ( ( string ) questToComplete != "" && ( string ) questToComplete != "all" )
                    {
                        break;
                    }
                    Thread.CurrentThread.Join( 200 );
                }
                else if ( ( string ) questToComplete != "" && ( string ) questToComplete != "all" && i == availableQuests )
                {
                    Bot.Printf( "Questname not found: {0}!", questToComplete );
                }
            }
            RoMScript( "SpeakFrame:Hide()" );
            Thread.CurrentThread.Join( 750 );
            return true;
        }

        public static string GetQuestStatus( object questnameorid )
        {
            var s = questnameorid as string;
            if ( s != null )
            {
                return ( string ) RoMScript( "igf_questStatus(\"" + NormaliseString( s ) + "\")" );
            }
            return ( string ) RoMScript( "igf_questStatus(" + questnameorid + ")" );
        }

        public static double SkillUsePrior
        {
            get
            {
                return Bot.Player.Profile.GetOptionValue<string>( "SKILL_USE_PRIOR" ) == "auto" ? GetPing() : Bot.Player.Profile.GetOptionValue<int>( "SKILL_USE_PRIOR" );
            }
        }

        public static int Ping
        {
            get { return GetPing(); }
        }

        public static KeyValuePair<string, int> ParseBuffName( string buffname )
        {
            if ( string.IsNullOrWhiteSpace( buffname ) )
                return new KeyValuePair<string, int>();

            string name;
            var count = 0;

            // First try and find '(3)' type count in name
            var tmpCount = Regex.Match( buffname, @"(\b\d+\b)" );
            if ( !string.IsNullOrWhiteSpace( tmpCount.Value ) )
            {
                count = Convert.ToInt32( tmpCount.Value );
                name = Regex.Match( buffname, "\b(.*)\b?[^(.0-9)]\b" ).Value;
                return new KeyValuePair<string, int>( name, count );
            }

            // Next try to find roman number
            return ParseRomanNumberInName( buffname );
        }

        public static KeyValuePair<string, int> ParseRomanNumberInName( string name )
        {
            var tmpCount = Regex.Match( name, @"\b([IVX]+)\b" );
            var count = 0;
            if ( !string.IsNullOrWhiteSpace( tmpCount.Value ) )
            {
                // Convert roman number to number
                if ( RomanNumbers.ContainsKey( tmpCount.Value ) )
                {
                    count = RomanNumbers[ tmpCount.Value ];
                }
                name = Regex.Match( name, "(.*)\b?[^\b(IVX)+\b]" ).Value;
                return new KeyValuePair<string, int>( name, count );
            }
            return new KeyValuePair<string, int>( name, 1 );
        }

    }

    public class ItemLink
    {
        public int Id { get; set; }
        public long Color { get; set; }
        public string Name { get; set; }
    }
}